-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding Cliffords to QuantumCircuits as Operations #7966
Conversation
…ng a preliminary Clifford optimization pass
Thank you for opening a new pull request. Before your PR can be merged it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient. While you're waiting, please feel free to review other open PRs. While only a subset of people are authorized to approve pull requests for merging, everyone is encouraged to review open pull requests. Doing reviews helps reduce the burden on the core team and helps make the project's code better for everyone. One or more of the the following people are requested to review this:
|
A few questions/comments came up in the discussion with @eliarbel and @ShellyGarion:
|
qiskit/circuit/operation.py
Outdated
@property | ||
@abstractmethod | ||
def definition(self): | ||
"""Definition of the operation in terms of more basic gates.""" | ||
raise NotImplementedError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we want to carry this over to Operation
since these are objects that we want to allow to not specify a decomposition at construction time (and especially, to not have to carry around a definition).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently multiple transpiler passes (Decompose
, Unroll3qOrMore
, etc.) require node.op.definition
, so this is a method that had to be implemented for Cliffords
and that imho Operation
should support. However, the main concern is already addressed (for Cliffords
): the definition circuit is not constructed at the gate creation time (and only when it's actually needed). For instance, the following code:
qc1 = QuantumCircuit(5)
qc1.append(random_clifford(3), [4, 0, 2])
qc1.append(random_clifford(3), [4, 0, 2])
qc1.append(random_clifford(3), [4, 0, 2])
qc2 = PassManager(OptimizeCliffords()).run(qc1)
qc3 = qc2.decompose()
only constructs the definition circuit for a single clifford in qc2
and only when decompose
is called.
@@ -75,7 +75,7 @@ def __init__(self, data, input_dims=None, output_dims=None): | |||
if isinstance(data, (list, np.ndarray)): | |||
# Default initialization from list or numpy array matrix | |||
self._data = np.asarray(data, dtype=complex) | |||
elif isinstance(data, (QuantumCircuit, Instruction)): | |||
elif isinstance(data, (QuantumCircuit, Operation)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a few other places where Instruction
should be updated to Operation
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I think I have updated all the places that I could find, but maybe I've still missed a few.
qiskit/circuit/quantumcircuit.py
Outdated
@@ -1160,7 +1161,7 @@ def _resolve_classical_resource(self, specifier): | |||
|
|||
def append( | |||
self, | |||
instruction: Instruction, | |||
instruction: Operation, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also update the docstring below, and potentially rename the argument from instruction
to operation
. (There's a util function https://github.com/Qiskit/qiskit-terra/blob/04807a13a7ba53d97c37bb092324e419296e3fba/qiskit/utils/deprecation.py#L19 to simplify the process of deprecating the original kwarg.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have updated the docstring. Would it be possible to postpone renaming/deprecation to a small follow-up PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't issue a deprecation warning until the alternative has been in place for a version. I wouldn't worry about it, though. Perhaps a long-term solution is (once we drop Python 3.7 support) to make this first argument positional-only (def append(self, instruction, /, qargs, cargs)
), since append
can also take a single CircuitInstruction
as of #8093, but really, it's a minor problem we don't need to touch right now.
qiskit/circuit/quantumcircuit.py
Outdated
@@ -1261,7 +1262,11 @@ def _append( | |||
|
|||
return instruction | |||
|
|||
def _update_parameter_table(self, instruction: Instruction) -> Instruction: | |||
def _update_parameter_table(self, instruction: Operation) -> Operation: | |||
# A generic Operation object at the moment does not require to have params. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this is the right time to reconsider whether Operation
s should require params
(I don't recall the exact reason they were removed from the PR implementing Operation
). If not, checking isinstance(instruction, Instruction)
seems more consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have updated the code to check isinstance(instruction, Instruction)
.
I don't remember the reason of not including params
in Operation
either. Possibly this would be a good idea, even though Cliffords
don't use params
to store stabilizer/destabilizer table. Do we want them to? Is it true that two Instructions
with the same name
, num_qubits
, num_clbits
and parameters are completely equivalent? Do we want this to be true?
@property | ||
def definition(self): | ||
"""Computes and returns the circuit for this Clifford.""" | ||
if self._definition is None: | ||
self._definition = self.to_circuit() | ||
|
||
return self._definition |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, how far do we make it through a transpile
call if we don't add a definition
? The long term goal is to have a transpiler pass which can synthesize Clifford
s and use the transpilation context to make some intelligent choices between different synthesis methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise, can to_instruction
above now return self
instead of converting to a circuit and then to an instruction?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how far do we make it through a transpile call if we don't add a definition?
I would say quite far, please see the code snippet from my answer to a previous question.
The long term goal is to have a transpiler pass which can synthesize Cliffords and use the transpilation context to make some intelligent choices between different synthesis methods.
I completely agree. This is also something that @ShellyGarion is interested in, as she wants to be able to choose at transpile time whether to apply a synthesis method that minimizes gate-count vs. depth and whether to apply a method that is better suited for all-to-all connectivity vs. linear-neighbor connectivity.
can to_instruction above now return self instead of converting to a circuit and then to an instruction?
This is an interesting suggestion (that I have not tried yet). I might be wrong about this, but I am afraid that certain methods (like constructing a unitary Operator
for a quantum circuit containing a clifford) depend on to_operation
translating more complex objects to simpler objects.
blocks.append(cur_block) | ||
prev_node = None | ||
cur_block = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor, but this could be wrapped in an if cur_block:
to avoid adding an empty []
to blocks
for every non-Clifford
instruction in the circuit.
This serves as an example of extra capabilities enabled by storing | ||
Cliffords natively on the circuit. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good. I think we should look to generalize CollectMultiQBlocks
or similar so that we can efficiently collect and grow blocks to resynthesize, but that's a separate issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should look to generalize CollectMultiQBlocks or similar so that we can efficiently collect and grow blocks to resynthesize, but that's a separate issue.
Agreed. This seems like some important functionality that comes up over and over.
blocks.append(cur_block) | ||
cur_block = [node] | ||
else: | ||
if prev_node.qargs == node.qargs: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if prev_node.qargs == node.qargs: | |
if set(prev_node.qargs) == set(node.qargs): |
Can we match here even if the qarg
orders don't match?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was also thinking about this, and this should be possible, but we need to suitably permute the rows and the columns of the clifford's stabilizer tableau.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand that the path to optimize this Clifford is very important. However, I am against extending the Clifford
in quantum_info to Operation/Instruction, etc. quantum_info and Circuit are on different levels of hierarchy in Qiskit, and confusion would create a circular dependency. For example, Operator is a class that represents operators, and unitary operators are represented using this class. But, this class is not Instruction. For unitary gates, another class UnitaryGate is used for QuantumCircuit. Therefore, it is safer to create the separate class such as CliffordGate or CliffordOperation to separate the hierarchy.
Another suggestion: how about finding clifford in Gate
s and compose it? (This could easily be done by putting an is_clifford
flag into the Gate.) Wouldn't it be complicated to have to wrap in Clifford
to represent Hadamard, S-Gate, etc.?
@ikkoham, thank you for your feedback! This PR is the result of a discussion that has been going on for quite some time, please see #5811 (and #7087), and in particular @kdk's comment #5811 (comment). You can view this as moving in the direction of high-level-synthesis, @kdk and @jakelishman can explain this better than me. Please note that we can already add
so to an extent the confusion already exists. From the transpilation point of view, the above currently implicitly converts
and then call this like
but frankly I don't see much benefit (and would require us to do many changes throughout the code). We have discussed the current status of this PR during the last Terra Circuits meeting, and we have agreed that we do not want to include @ikkoham, to see if I understand your second point. You are suggesting a transpiler pass that would collect blocks of consecutive Clifford gates, and merge these into a single |
@alexanderivrii Thank you for your reply. I have no objection to the hierarchy of quantum circuits, Operation and Instruction.
Your example is just using implicit conversions. Without implicit conversion, we have qc = QuantumCircuit(4)
cliff = random_clifford(2)
qc.append(cliff.to_instruction(), [1, 3]) and Clifford itself is NOT If Clifford is made more complicated, it will be impossible to maintain. Personally, I believe that better code is one in which each module is loosely coupled. This direction of tight coupling makes maintenance difficult; the introduction of CliffordGate helps to weaken the coupling between modules. (But, in past discussions, I have seen that And, for if isinstance(instruction, Clifford):
instruction = CliffordGate(inst) This method allows
Yes. That is what I want to say. Thanks. |
@ikkoham: we totally agree with you - we're very much not trying to add any meaningful extra code to The below is a very rough sketch of one possible idea, but it's still under discussion and not decided at all: We will define an
|
HighLevelSynthesis transpiler pass, and incorporating it into the preset pass managers
…oadcast from Operation interface and in particular from Clifford
Please refer to @jakelishman's proposal for Based on the recent discussions, I have removed both The code in this PR is significantly closer to the above proposal. Regarding In the future, we probably want each synthesis algorithm to be available via a plugin interface, similarly to the unitary synthesis plugin interface, thus As of now, I have modified the 4 preset passmanagers to run There is also a question whether the Regarding |
…pose; slightly changing Decompose pass to skip nodes with definition attribute
@mtreinish, thank you for the review. I have improved/expanded the description in the release notes, and have added a TODO comment in Even though the current code works correctly for Cliffords, as you have pointed out, there is a problem in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making the updates. This mostly LGTM for now. The only thing I'd like to see resolved is the use of Operation.to_matirx
in Operator
: https://github.com/Qiskit/qiskit-terra/pull/7966/files#r941549919 I think as long as we outline that to_matrix()
is an optional thing on operations and that the code handles the path when we have an opaque instruction without a matrix representation that is sufficient to resolve this. Once that is fixed I think I'm fine approving this.
@mtreinish, is the code comment in Another thought is that if someone really wants to construct an |
I was thinking of having it as part of the documentation but I guess a comment is fine too if we don't want to advertise it. But I think you're right maybe we should just drop this piece as it really isn't a logical part of the operation class and revert back to using
that would break when it previously worked right? Maybe we just should special case things and do an isinstance for an Instruction or a clifford explicitly and leave a comment saying it's for backwards compatibility. |
…ford (for backward compatibility)
Yes, this used to work, and some people (like @ShellyGarion) depend on this functionality. So we should support this for backward compatibility.
This is a perfect suggestion, done in ab213f2. On the one hand, we don't include things to Operation's API, on the other hand there is no under-the-table non-documented functionality. And we can always revisit things in the future as more higher-level-operations will become available. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This LGTM now, thanks for sticking with this and making all the necessary updates.
Summary
In this PR, we are adding a
Clifford
object to aQuantumCircuit
as anOperation
, without first explicitly converting thisClifford
into aGate
.An important point is that a
Clifford
is no longer decomposed as soon as it is added to aQuantumCircuit
. The proceduredecompose_clifford
only runs when thedefinition
of aClifford
is required, which might only be during a later transpiler pass.To illustrate an additional benefit of adding
Clifford
s natively, there is a preliminary transpiler passOptimizeCliffords
that uses the ability to composeClifford
s.In the near future, other objects will be added as
Operation
s as well.Details and comments
A
Clifford
object does not inherit fromInstruction
. In order to work withClifford
s natively, it must support the following:broadcast_arguments
: this is used for expanding (aka broadcasting) arguments. Note that there is an additional PR to improve the broadcasting code.condition
: as far as I understand, this is used for conditional if instructions. I don't think it applies toClifford
s, but is currently required bycircuit_to_dag
. I believe this is something that we can later clean up._directive
: This is a attribute to treat like barrier for transpiler, unroller, drawer. It is currently required byDagCircuit
. Again I think that this may be something that we can later clean up.definition
: This is the function that builds the definition of theClifford
in terms of more basic gates (i.e. the function that essentially callsdecompose_clifford
).This PR essentially restores #7087, but with a few differences:
Instruction
now also inherits fromOperation
. As @jakelishman commented during the Qiskit Circuits and Transpiler meeting, this is actually the correct thing to do: instructions are something that we add to quantum circuits, even if we later choose to deprecateInstruction
altogether (and moreover this would greatly simplify the deprecation process).CNOTDihedral
andPauli
do not yet inherit fromOperation
. These (and some other classes) will be added toOperation
in a later PR (and some experiments are needed to make sure that inheritingPauli
fromOperation
does not lead to worse performance).QuantumCircuit
itself also does not yet inherit fromOperation
(and possibly some more thought is required whether it eventually should and what would be the additional benefit of this -- as we can already append one quantum circuit to another).In order to turn
condition
into a method ofOperation
, we needed to add the propertycondition
toInstruction
.